﻿/*========================== 
 * @author 郑金泉
 * @desc MVC 路由
 * ========================= */
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.SessionState;
using System.IO;
using System.Threading;

using Obsidian;
using Obsidian.Config;
using Obsidian.Runtime;
using Obsidian.Utils;
using Obsidian.Data;
using Obsidian.Web;

namespace Obsidian.Mvc
{
    public class Route : IHttpHandler, IRequiresSessionState
    {

        private const string PN_ACTION = "act";
        private const string PN_CACHEABLE = "_cacheable_";
        private const string HTML_CACHE_KEY = "obsidian_html_cache_";

        public void ProcessRequest(HttpContext context)
        {
            if (!this.ExecuteController(context))
            {
                //this.ExecuteUrl(context);
            }
        }

        public bool ExecuteController(HttpContext context)
        {
            string action;
            if (!ParamsReceiver.TryGetString(PN_ACTION, out action))
            {
                string url = context.Request.RawUrl.ToLower();

                int m = url.IndexOf("?");
                if (m > 0)
                {
                    url = url.Substring(0, m);
                }
                int n = url.IndexOf(".");
                if (n > 0)
                    action = url.Substring(1, n - 1);
                else
                    action = url.Substring(1);
            }
            return ExecuteController(context, action);
        }

        public bool ExecuteController(HttpContext context, string action)
        {
            string[] arrStr = action.Split('-');

            string typeName;
            string methodName;
            if (arrStr.Length == 1)
            {
                typeName = arrStr[0];
                methodName = MvcConfig.DefaultAction;
                if (string.IsNullOrEmpty(methodName))
                    throw new Exception("未设置defaultAction配置");
                action = action + "-" + methodName;
            }
            else
            {
                StringBuilder sb = new StringBuilder();
                bool first = true;
                for (int i = 0; i < arrStr.Length - 1; i++)
                {
                    if (first) first = false;
                    else sb.Append(".");
                    sb.Append(StringUtil.FirstUpper(arrStr[i]));
                }
                typeName = sb.ToString();

                methodName = arrStr[arrStr.Length - 1];
                if (this.ExecuteAction(context, action, typeName, methodName))
                    return true;

                typeName = typeName + "." + methodName;
                methodName = MvcConfig.DefaultAction;
                action = action + "-" + methodName;
                if (string.IsNullOrEmpty(methodName))
                    return false;
            }

            return this.ExecuteAction(context, action, typeName, methodName);
        }

        private bool ExecuteAction(HttpContext context, string action, string typeName, string methodName)
        {
            MvcConfig.MvcCtrlInfo configCtrl = MvcConfig.CtrlInfo;

            string fullTypeName = configCtrl.Namespace + "." + typeName + configCtrl.TypeSuffix;

            Type t;
            TypeUtil.TryFindType(fullTypeName, true, out t);
            if (t == null)
                return false;
            object obj = Activator.CreateInstance(t);
            if (obj == null)
                return false;
            MethodInfo method = obj.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
            if (method == null)
                return false;

            //使用缓存执行请求
            if (this.ProcessRequestWithCached(context, action))
                return true;

            if (AppConfig.IsDebug)
            {
                method.Invoke(obj, new Object[] { context });
                OView.RemoveSessionViews();
            }
            else
            {
                try
                {
                    method.Invoke(obj, new Object[] { context });
                }
                catch (Exception ex)
                {
                    this.ProcessCtrlException(ex, context, typeName);
                }
                finally
                {
                    OView.RemoveSessionViews();
                }
            }
            return true;
        }

        private void ProcessCtrlException(Exception ex, HttpContext context, string ctrlName)
        {
            StringBuilder sbError = new StringBuilder();
            sbError.AppendLine("控制器异常：" + ctrlName);
            sbError.AppendLine("URL：" + context.Request.RawUrl);

            string errorMsg = sbError.ToString();
            string errorCode = Logger.Error(ex, errorMsg);

            if (ErrorPage.Has(ErrorPage.StatusCode.Status500))
            {
                Dictionary<string, object> dict = new Dictionary<string, object>();
                dict["code"] = errorCode;
                ErrorPage.Redirect(ErrorPage.StatusCode.Status500);
            }
            else
            {
                HttpResponse response = context.Response;
                response.Write("程序异常：" + errorCode);
            }
        }

        public void ExecuteUrl(HttpContext context)
        {
            /*
            string absolutePath = request.Url.AbsolutePath;
             * */
            HttpServerUtility server = context.Server;
            HttpRequest request = context.Request;
            string filePath = server.MapPath(request.RawUrl);
            if (File.Exists(filePath))
            {
                try
                {
                    context.Server.Transfer("~" + request.RawUrl, false);
                }
                catch (Exception ex)
                {
                    context.Response.Write(ex.Message);
                }
            }
        }

        #region 缓存处理相关

        private bool ProcessRequestWithCached(HttpContext context, string action)
        {
            bool cacheable = true;
            if (!ParamsReceiver.TryGetBoolean(PN_CACHEABLE, out cacheable))
                cacheable = true;

            if (MvcConfig.Cacheable && cacheable)
            {
                string host = WebUtil.GetCurrentHost();
                string url = context.Request.Url.ToString();
                string cacheKey = MvcConfig.CreateMvcPageCacheKey(host, url, action);
                if(String.IsNullOrEmpty(cacheKey))
                    return false;
                MvcConfig.MvcCacheInfo cacheInfo = MvcConfig.GetCacheSetting(cacheKey);
                if (cacheInfo != null)
                {
                    string html = this.GetHtmlWithCached(context, cacheKey, cacheInfo);
                    if (!String.IsNullOrEmpty(html))
                    {
                        context.Response.Write(html);
                        return true;
                    }
                }
            }
            return false;

        }

        private string GetHtmlWithCached(HttpContext context, string cacheKey, MvcConfig.MvcCacheInfo cacheInfo)
        {

            bool refresh = false;
            int intRc = 0;
            if (ParamsReceiver.TryGetInt32(MvcConfig.PN_REFRESH_CACHE, out intRc) && intRc == 1)
                refresh = true;
            if (!refresh)
                ParamsReceiver.TryGetBoolean(MvcConfig.PN_REFRESH_CACHE, out refresh);

            object val = null;
            if (refresh)
                MemCached.Delete(cacheKey);
            else
                val = MemCached.Get(cacheKey);

            string cacheContent;
            if (val == null)
                return this.GetRequestHtmlAndSetCache(context, cacheKey);

            cacheContent = Convert.ToString(val);
            int n = cacheContent.IndexOf("|");
            if (n < 0)
                return this.GetRequestHtmlAndSetCache(context, cacheKey);

            string strCacheTime = cacheContent.Substring(0, n);
            DateTime cacheTime;
            //时间转换是否有效
            if (!DateTime.TryParse(strCacheTime, out cacheTime))
                return this.GetRequestHtmlAndSetCache(context, cacheKey);
            //时间是否过期
            DateTime expires = cacheTime.AddSeconds(cacheInfo.Seconds);
            if (expires.CompareTo(DateTime.Now) <= 0)
                return this.GetRequestHtmlAndSetCache(context, cacheKey);

            string html = cacheContent.Substring(n + 1);

            return html;

        }

        private string GetRequestHtmlAndSetCache(HttpContext context, string cacheKey)
        {
            string url = context.Request.Url.ToString();
            Hashtable ht = new Hashtable();
            ht[PN_CACHEABLE] = "false";
            string status;
            string html = HttpRequestor.Post(url, ht, out status);

            string cacheContent = DateTimeUtil.NowString + "|" + html;
            MemCached.Set(cacheKey, cacheContent);
            return html;
        }

        #endregion

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}
